<?php
/*
 * Copyright (c) 2008 Sergey Bondari, bondari@bondari.net
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

namespace Mosaic\System;


/*
 * TTY font color sequences
 */
const TTY_BLACK = 30;
const TTY_RED = 31;
const TTY_GREEN = 32;
const TTY_YELLOW = 33;
const TTY_BLUE = 34;
const TTY_MAGENTA = 35;
const TTY_CYAN = 36;
const TTY_WHITE = 37;


/*
 * TTY background color sequences
 */
const TTY_BG_BLACK = 40;
const TTY_BG_RED = 41;
const TTY_BG_GREEN = 42;
const TTY_BG_YELLOW = 43;
const TTY_BG_BLUE = 44;
const TTY_BG_MAGENTA = 45;
const TTY_BG_CYAN = 46;
const TTY_BG_WHITE = 47;


/**
 * System loggin and debugging handlers and routines
 * @package System
 * @subpackage Debug
 */
class Debug {

	/**
	 * Log file handler
	 * @var resource
	 */
	private $fLog;

	/**
	 * stderr handler
	 * @var resource
	 */
	private $stderr;

	/**
	 * Activation flag
	 * @var boolean
	 */
	private $logEnabled;



	/**
	 * Class constructor
	 */
	function __construct() {
		global $sys;
		if ($k = $sys->config->get('debug.log.enabled')) {
			$fname = $sys->config->get('debug.log.file');
			if ($fname) {
				$this->fLog = fopen($fname, $sys->config->get('debug.log.append') ? 'a' : 'w');
				if (!$this->fLog) {
					throw new \Exception('Cannot open '.$fname.' for writing. Check permissions');
				}
			}

			if ($sys->config->get('debug.log.stderr')) {
				$this->stderr = fopen('php://stderr','w');
			}

		}
		set_error_handler(array($this, 'errorHandler'));
		set_exception_handler(array($this, 'exceptionHandler'));

		assert_options(ASSERT_ACTIVE, 1);
		assert_options(ASSERT_WARNING, 0);
		assert_options(ASSERT_QUIET_EVAL, 1);
		assert_options(ASSERT_CALLBACK, array($this,'assertHandler'));
	}


	/**
	 * PHP error handler
	 *
	 * @param integer Code
	 * @param string Description	
	 * @param string File
	 * @param integer Line of code
	 * @return void 
	 */
	public function errorHandler($errno, $errstr, $errfile, $errline) {
		global $sys;
		static $lastTime;

		/*
		 * Obey error_reporting() settings
		 */
		if (($errno & error_reporting()) == 0 ) return;


		/*
		 * Calculate time delta from the last error event
		 */
		if (!$lastTime) $lastTime = microtime(true);
		$timeDelta = microtime(true) - $lastTime;
		$lastTime = microtime(true);


		if ($this->fLog && $errno < 2048) {
			switch ($errstr[0]) {
				case '+':
				case '-':
					$levelChange = $errstr[0];
					$errstr = ltrim($errstr,'+-');
				break;
				default:
					$levelChange = '#';
			}

			$role = (isset($sys) ? $sys->role : 0);

			fputs($this->fLog, sprintf("%d\t%s\t%0.2f\t%d\t%s\t%s:%s\t%d\n", $role, $levelChange, round($timeDelta*100)/100, $errno, $errstr, $errfile, $errline, memory_get_usage()));

			if (!$this->stderr) return;


			/*
			 * Prepare and output nice colored message to stderr
			 */


			switch ($role) {
				case PRECURSOR:
					$pidFont = TTY_WHITE;
					$pidBg = TTY_BG_BLACK;
				break;

				case WORKER:
					$pidFont = TTY_WHITE;
					$pidBg = TTY_BG_BLUE;
				break;

				case REQUEST_BROKER:
					$pidFont = TTY_WHITE;
					$pidBg = TTY_BG_MAGENTA;
				break;

				case OBJECT_BROKER:
					$pidFont = TTY_BLACK;
					$pidBg = TTY_BG_CYAN;
				break;

				case NAMESERVER:
				default:
					$pidFont = TTY_WHITE;
					$pidBg = TTY_BG_BLACK;
				break;

			}

			switch ($errno) {
				case E_USER_WARNING:
					$sFont = TTY_BLACK;
					$sBg = TTY_BG_YELLOW;
					$errCode = 'WRN';
				break;
				
				case E_USER_NOTICE:
					$sFont = $pidFont;
					$sBg = $pidBg;
					$errCode = 'LOG';
				break;

			
				case E_USER_ERROR:
				default:
					$sFont = TTY_WHITE;
					$sBg = TTY_BG_RED;
					$errCode = 'ERR';
				break;



			}

			$mFont = TTY_WHITE;
			$mBg = TTY_BG_BLACK;

			$pidTTY = chr(27)."[${pidFont};${pidBg}m";
			$statTTY = chr(27). "[${sFont};${sBg}m";

			$msgTTY = chr(27) . "[${mFont};${mBg}m";

			$out = sprintf("%s\n%s+%0.2f%d %s%s %s  %s:%s", $pidTTY, $statTTY, $timeDelta, posix_getpid(), $errCode, $pidTTY, $errstr, $errfile, $errline).$msgTTY;
			fputs($this->stderr, $out);
		}

	}
	
	/**
	 * Unhandled exceptions handler
	 *
	 * @param Exception
	 * @return void 
	 */
	public function exceptionHandler($e) {
		$this->errorHandler(E_USER_ERROR, sprintf('Uncatched exception: %s', $e->getMessage()), $e->getFile(), $e->getLine()); 
	}
	

	/**
	 * Assertions handler
	 *
	 * @param Exception
	 * @return void 
	 */
	public function assertHandler($file, $line, $code) {
		$this->errorHandler(E_USER_ERROR, sprintf('Assertion \'%s\' failed', $code), $file, $line);
	}


	/**
	 * Class destructor
	 */
	function __destruct() {
		if ($this->fLog) fclose($this->fLog);
		restore_error_handler();
	}



}
